定期ミートアップ 第7回 yhara
BiwaSchemeにhygenic macroを入れる
hygenic macroとは?
今回は仕組みの話はしない(解説できるほど分かってないので)
Lispとは
Lispは言語名ではない
世界4大Lispyhara.icon
Common Lisp
Scheme
Clojure
Emacs Lisp
S式
構文木マクロ
構文木マクロとは
単にマクロというとC言語の#defineとかも含まれるので…
「プログラムを受け取ってプログラムを生成するプログラム」
Lisp
Rust
Scala
Template Haskell
Elixir
Crystal
例
code:scm
(define-macro (test expr)
(list 'unless expr
(list 'display "ng: ")
(list 'display (list 'quote expr))
(list 'newline))
(test (= 1 1))
(test (= 1 2)) ;=> ng: (= 1 2)
(通常は以下のようにquasiquoteをつかう)
code:scm
(define-macro (test expr)
`(unless ,expr
(display "ng: ")
(display (quote ,expr))
(newline)))
展開後のプログラム
code:scm
(unless (= 1 2)
(display "ng: ")
(display (quote (= 1 2)))
(newline))
問題点
define-macroはCommon Lispのdefmacroをそのまま持ってきたようなもの
SchemeとCommon Lispは言語仕様がいろいろ違う
CLのシンボルはメタデータを持てるが、Schemeのシンボルは単なる文字列
Schemeは変数名と関数名のネームスペースが同じ
名前が衝突する場合がある
a) マクロ内で名前を生成する場合(tmpみたいによくある名前にすると危ない)
b) 変数名と関数名が衝突する場合(listという標準関数があるが、うっかりすると変数名に使いそう)
hygenic macroではこれらの問題を解決できる
「こっちのtmp」と「あっちのtmp」「こっちのlistとあっちのlist」を区別できる
こっち=マクロ定義側の環境
あっち=マクロ仕様側の環境
hygenic macroを持っている言語
Scheme, Rustは変数名と関数名のネームスペースが同じ
Dylan/Nim/Juliaもそうなんですかね?yhara.icon
作戦
うまくいけばhygenic macroとlibrary system (import/export)が同時に手に入る
libraryがグローバル変数の名前空間をもつ
階層構造
1. syntactic closure
2. er-macro-transformer (er = explicit renaming)
3. syntax-rules (R7RS small)
syntax-rules
パターンと置き換え
code:scm
(define-syntax test
(syntax-rules ()
((_ expr) ; このパターンにマッチしたとき、
(unless expr ; この内容(テンプレート)に置き換える
(display "ng: ")
(display (quote expr))
(newline)))))
(test (= 1 1))
(test (= 1 2))
クオートは自動で行われる
er-macro-transformer
syntax-rulesと比べると低レベルな仕組み
「こっち」のものにrenameでマークを付ける
code:scm
(define-syntax test
(er-macro-transformer
(lambda (form rename compare)
(let ((expr (cadr form)))
`(,(rename 'unless) ,expr
(,(rename 'display) "ng: ")
(,(rename 'display) ',expr)
(,(rename 'newline)))))))
(test (= 1 1))
(test (= 1 2))
逆に「あっち」のものにマークを付ける仕組み(implicit-renaming)もあるが、計算量が高いらしい
進捗
なんとなくいけそうな感じがしてきたところ
残作業
ifなどの組み込み構文をすべて実装する
いまのVMとの統合
----
今回しなかった話
syntax-case / 名前の注入
Shiro(2018/11/25 00:21:42 UTC): Hygienic macroに必要なのは、マクロ定義環境とマクロ呼び出し環境を区別することで、er-macroの場合は「マクロ定義環境はリネーム」「マクロ呼び出し環境は生シンボル」という区別でそれを実現しています。R6RSの低レベルマクロのdatum->syntaxは一歩進んで、任意の識別子について紐つけられた環境を取り出せる(別の識別子につけかえられる)ようにしていて、それはhygienic macroの要求仕様の外になりますが、確かにそのままのer-macroには無い機能です。renameした識別子に環境情報をぶらさげておけばいいんですが、マクロ呼び出し環境で参照されることを期待して挿入した生シンボルについては、そのまま持ち運ぶと更なるマクロ呼び出しで環境情報が失われる場合があるので、er-macro展開後に環境情報を付加してやらないとなりません。
syntax-rules のリファレンス実装は、 syntax-rules マクロから er-macro-transformer マクロへ変換します。 そのとき、 元のマクロのリテラルを変換後にも同じ意味のまま受け渡すために使っています。 変換された er-macro-transformer マクロでリテラルを使うにはクォートする必要があるのですけど、 通常の quote 構文では構文情報を strip してしまいます。